package jdesktop.appbar;

import javax.swing.event.SwingPropertyChangeSupport;
import java.beans.IndexedPropertyChangeEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;

/**
 * Adds the ability to freeze property change notifications, and send all notifications as
 * a single event.
 * <p>
 * Unlike
 */
public class PropertyChangeSourceSupport implements PropertyChangeSource
{
    static public final String FREEZE_NOTIFICATION = "freezeNotification";


    private final PropertyChangeSupport   pcs;
    private final Object                  source;

    private boolean freezeNotification;

    /**
     * The events that have not been sent.
     */
    private List<PropertyChangeEvent> pendingEvents = new ArrayList<PropertyChangeEvent>();

    public PropertyChangeSourceSupport()
    {
        this(false);
    }

    public PropertyChangeSourceSupport(boolean fireOnEDT)
    {
        source = this;
        pcs = new SwingPropertyChangeSupport(source, fireOnEDT);
    }

    public PropertyChangeSourceSupport(Object source, boolean fireOnEDT)
    {
        this.source = source;
        pcs = new SwingPropertyChangeSupport(source, fireOnEDT);
    }

    public boolean isFreezeNotification()
    {
        return freezeNotification;
    }

    /**
     * Sets a new freezeNotification.
     * @param freezeNotification    The new value for {@link #FREEZE_NOTIFICATION}.
     * @return  The prevvious value. This can be used to create "nested" freeze ranges.
     */
    public boolean setFreezeNotification(boolean freezeNotification)
    {
        boolean old = this.freezeNotification;
        this.freezeNotification = freezeNotification;
        return old;
    }

    /**
     * Fires any pending events when notifications are frozen.
     * If events are not frozen, there will be no pending
     * events so this method will have no effect.
     * @return <code>true</code> if there were pending events that were fired.
     */
    public boolean firePendingEvents()
    {
        PropertyChangeEvent aggregate = createAggregateEvent();
        if (aggregate!=null)
            reallyFirePropertyChange(aggregate);
        return aggregate!=null;
    }

    protected PropertyChangeEvent createAggregateEvent()
    {
        PropertyChangeEvent evt = null;
        int size = pendingEvents.size();
        if (size>0)
        {
            if (size==1)
                evt = pendingEvents.get(0);
            else
            {
                evt = new CompositePropertyChangeEvent(source,
                        new ArrayList<PropertyChangeEvent>(pendingEvents));
            }
            pendingEvents.clear();
        }
        return evt;
    }

    protected void fireOrQueuePropertyChange(String propertyName, Object oldValue, Object newValue)
    {
        PropertyChangeEvent evt = createEvent(propertyName, oldValue, newValue);
        fireOrQueuePropertyChange(evt);
    }

    protected void fireOrQueueIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue)
    {
        PropertyChangeEvent evt = new IndexedPropertyChangeEvent(this, propertyName, oldValue, newValue, index);
        fireOrQueuePropertyChange(evt);
    }

    private void fireOrQueuePropertyChange(PropertyChangeEvent evt)
    {
        if (isFreezeNotification())
            this.pendingEvents.add(evt);
        else
            reallyFirePropertyChange(evt);
    }

    /**
     * Directly instructs the PropertyChangesupport to fire a propertyChangeEvent.
     * @param evt   The event to fire.
     * @see #fireOrQueuePropertyChange(java.beans.PropertyChangeEvent)
     */
    private void reallyFirePropertyChange(PropertyChangeEvent evt)
    {
        pcs.firePropertyChange(evt);
    }

    protected PropertyChangeEvent createEvent(String propertyName, Object oldValue, Object newValue)
    {
        return new PropertyChangeEvent(source, propertyName, oldValue, newValue);
    }


    public void addPropertyChangeListener(PropertyChangeListener listener)
    {
        pcs.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener)
    {
        pcs.removePropertyChangeListener(listener);
    }

    public PropertyChangeListener[] getPropertyChangeListeners()
    {
        return pcs.getPropertyChangeListeners();
    }

    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener)
    {
        pcs.addPropertyChangeListener(propertyName, listener);
    }

    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener)
    {
        pcs.removePropertyChangeListener(propertyName, listener);
    }

    public PropertyChangeListener[] getPropertyChangeListeners(String propertyName)
    {
        return pcs.getPropertyChangeListeners(propertyName);
    }

    public void firePropertyChange(String propertyName, Object oldValue, Object newValue)
    {
        if (oldValue!=newValue)
            fireOrQueuePropertyChange(propertyName, oldValue, newValue);
    }

    public void firePropertyChange(String propertyName, int oldValue, int newValue)
    {
        if (oldValue!=newValue)
            fireOrQueuePropertyChange(propertyName, oldValue, newValue);
    }

    public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue)
    {
        if (oldValue!=newValue)
            fireOrQueuePropertyChange(propertyName, oldValue, newValue);
    }

    public void firePropertyChange(PropertyChangeEvent evt)
    {
        fireOrQueuePropertyChange(evt);
    }

    public void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue)
    {
        if (oldValue!=newValue)
            fireOrQueueIndexedPropertyChange(propertyName, index, oldValue, newValue);
    }

    public void fireIndexedPropertyChange(String propertyName, int index, int oldValue, int newValue)
    {
        if (oldValue!=newValue)
            fireOrQueueIndexedPropertyChange(propertyName, index, oldValue, newValue);
    }

    public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue)
    {
        if (oldValue!=newValue)
            fireOrQueueIndexedPropertyChange(propertyName, index, oldValue, newValue);
    }

    public boolean hasListeners(String propertyName)
    {
        return pcs.hasListeners(propertyName);
    }

    public class CompositePropertyChangeEvent extends PropertyChangeEvent
    {
        private final List<PropertyChangeEvent> changes;

        public CompositePropertyChangeEvent(Object source, List<PropertyChangeEvent> changes)
        {
            super(source, null, null, null);
            this.changes = changes;
        }

        public List<PropertyChangeEvent> getChanges()
        {
            return changes;
        }
    }
}
